package risoe.syslab.fhnew.controller.plugin;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;

import risoe.syslab.fhnew.common.SystemConstants.DeviceType;
import risoe.syslab.fhnew.config.ActuatorConfig;
import risoe.syslab.fhnew.config.RoomConfig;
import risoe.syslab.fhnew.config.SensorConfig;
import risoe.syslab.fhnew.controller.ControllerConfig;
import risoe.syslab.fhnew.controller.PluginController;
import risoe.syslab.fhnew.hw.Actuator;
import risoe.syslab.fhnew.hw.Sensor;
import risoe.syslab.model.CompositeMeasurement;

/**
 * A PluginController for demonstrating presence-based lighting.
 * @author M. Svendsen
 */
public class PresenceBasedLightController extends PluginController {
	// Constants
	public final static String NAME = "Presence-Based Light Controller";
	public final static String OWNER = "masv";

	// Containers
	private static HashMap<String, ControllerState> mControllerMap = new HashMap<String, ControllerState>();

	public static void main(String[] args) {
		try {
			String configFile = args.length > 0? args[0]: null;
			new PresenceBasedLightController(NAME, OWNER, configFile);
		} catch (RemoteException e) {
			// RMI Connection was unsuccessful 
			System.out.println("RMI Error: "+e.getMessage());
			System.exit(0);
		} catch (InstantiationException e) {
			// Controller name was already taken, or one of the requested devicetypes was already taken control of
			// another plugincontroller
			System.out.println("Argument Error: "+e.getMessage());
			System.exit(0);
		}
	}

	public PresenceBasedLightController(String name, String owner, String cfgfile) throws RemoteException, InstantiationException {
		super(name, owner, cfgfile);
	}

	@Override
	public void initialStateReceived(Actuator[] actuators) {
		// Init controllers for all rooms
		ArrayList<RoomConfig> rooms = getBuildingConfig().getRooms();
		for (RoomConfig room : rooms){
			String rname=room.getRoomName();
			mControllerMap.put(rname, new ControllerState(room));
		}
	}

	@Override
	public void sensorUpdated(Sensor s) {
		/** If this sensor is a presence sensor, switch lights in room on or off */
		if (s.getType() == DeviceType.Motion) {
			RoomConfig room=getBuildingConfig().getRoomForSensor(s.getName());
			CompositeMeasurement tm=s.getValue();
			if (tm.isGoodProcessValue()) {
				// Check if lights should change state
				float newState =  mControllerMap.get(room.getRoomName()).toggle(s);

				// Check if state should NOT change
				if (newState == -1) return;

				// Presence change detected and new state is in effect! Turn on/off lights
				for (ActuatorConfig actuator : room.getActuators()){
					if (actuator.getType() == DeviceType.DimmableLight){
						setActuator(actuator.getName(), new CompositeMeasurement(newState));
					}
				}
			}
		}		
	}
	
	@Override
	public ControllerConfig supplyControllerConfig(ControllerConfig config) {
		return config.addControlledDeviceType(DeviceType.DimmableLight).addTypeSubscription(DeviceType.Motion);
	}

	/**
	 * A class for facilitating presence-based control of lighting in a room
	 * @author M. Svendsen
	 */
	private class ControllerState{
		private boolean mOn = false;
		private final RoomConfig mRoom;
		private HashMap<String, Boolean> mSensorTrackers = new HashMap<String, Boolean>();

		/**
		 * Initializes a brightness controller
		 * @param the room to be controlled
		 */
		public ControllerState(RoomConfig room){
			mRoom = room;

			for (SensorConfig sensor : room.getSensors()){
				if (sensor.getType() == DeviceType.Brightness){
					// This is a brightness sensor in this room
					mSensorTrackers.put(sensor.getName(), false);
				}
			}
		}

		/**
		 * Called when a presence sensor in a room turns on or off
		 * @param sensor that triggered this event
		 * @return 1.0f if the lights in this room should be turned on, 0.0f if they should be turned off and -1.0f for the state
		 * to remain unchanged
		 */
		public int toggle(Sensor sensor){
			String name = sensor.getName();
			float value = (float) sensor.getValue().value;

			// Only one positive reading is required to turn ON the lights.
			if (value == 1.0f){
				if (mOn) return -1;
				mOn = true;

				return 1;
			}

			// All presence sensors must agree on no presence to turn OFF the lights. 
			boolean allAgreed = true;
			for (SensorConfig sensorInRoom : mRoom.getSensors()){
				if (sensorInRoom.getType() == DeviceType.Presence){
					if (!sensorInRoom.getName().equals(name)){
						// Check if all presence sensors agree on no presence
						CompositeMeasurement measurement = getSensorValue(sensorInRoom.getName());
						if (measurement.value == 1.0){
							// Sensors do not agree. Do nothing
							allAgreed = false;
							break;
						}
					}
				}
			}

			// All sensors agree that the light should be turned off
			if (allAgreed){
				mOn = false;
				return 0;
			}

			// No change in state
			return -1;
		}
	}
}


